/**
 *  JFTSTool - Java Full Text Search tool.
 *  Copyright (C) <2009>  <grupo JFTS>
 *  This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.jftstool.indexer.document.constructor;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.jftstool.config.DBAttributeConfig;
import org.jftstool.config.DBConnectionConfig;
import org.jftstool.config.DBDocumentExtractorConfig;
import org.jftstool.config.DBQueryDocumentConfig;
import org.jftstool.config.JFTSConfiguration;
import org.jftstool.config.exceptions.JFTSConfigException;
import org.jftstool.enums.DataTypes;
import org.jftstool.indexer.document.DBDocument;
import org.jftstool.indexer.document.exceptions.DBDocumentExtractorException;

/**
 * <p>Abstract class that must be inherited to build a class that extracts<br>
 * documents in a BD.</p>
 * @author grupo JFTS
 */
public abstract class DBDocumentsExtractor {
	protected DBDocumentExtractorConfig mainConfig = null;
	protected DBConnectionConfig dbConnConfig = null;
	protected DBQueryDocumentConfig dbQueryConfig = null;
	protected Connection conn = null;
	protected PreparedStatement pstmt = null;	
	protected ResultSet rs = null;

	
	public DBDocumentsExtractor() throws DBDocumentExtractorException {
		try {
			mainConfig = JFTSConfiguration.getInstance().getDBConfig();
		}
		catch (JFTSConfigException e) {
			throw new DBDocumentExtractorException(e.getMessage());
		}
	}
	
	/**
 	* Create a connection to the database.
 	* @param connName Connection name to use (connection parameterized in config file.)
 	* @throws DBDocumentExtractorException
	*/
	public void createConnection(String connName) throws DBDocumentExtractorException {
		dbConnConfig = mainConfig.getDBConnection(connName);
		if (dbConnConfig == null){
			throw new DBDocumentExtractorException("Error, can't find DBConnectionConfig: " + connName); 
		}
		else{
			try {
				Class.forName(dbConnConfig.getArg("driver"));
				conn = DriverManager.getConnection(dbConnConfig.getArg("url"), dbConnConfig.getArg("username"), dbConnConfig.getArg("password"));
				conn.setAutoCommit(false);				
			}
			catch (ClassNotFoundException e) {
				_closeConn();
				throw new DBDocumentExtractorException(e.getMessage());				
			}
			catch (SQLException e) {
				_closeConn();				
				throw new DBDocumentExtractorException(e.getMessage());				
			}
		}
	}
	
	/**
	 * This method create and execute the prepareStatement.
	 * @param dbQueryName
	 * @throws DBDocumentExtractorException
	 */
	public void createStatement(String dbQueryName) throws DBDocumentExtractorException {
		dbQueryConfig = mainConfig.getDBQuery(dbQueryName);
		if (dbQueryConfig == null){
			throw new DBDocumentExtractorException("Error, can't find DBQueryDocumentConfig: " + dbQueryName); 
		}
		else{
			Boolean validate = _validateATTOUT(dbQueryConfig.getAttOUT());
			if (validate){
				try {

					pstmt = conn.prepareStatement(dbQueryConfig.getSql(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
					pstmt.setFetchSize(Integer.MIN_VALUE);
					
					if (pstmt.getParameterMetaData().getParameterCount() != dbQueryConfig.getAttIN().size()){
						_closeConn();						
						throw new DBDocumentExtractorException("Error, mismatch the number of parameters of the sql query and DBQueryDocument");
					}
					else{
						
						//Set the parameters to the prepareStatement
						int index = 1;
						for (DBAttributeConfig att : dbQueryConfig.getAttIN()){
							pstmt.setObject(index, att.getArg("value"));
						}
						
						//Execute prepareStatment
						rs = pstmt.executeQuery();
						
						if (rs.getMetaData().getColumnCount() != dbQueryConfig.getAttOUT().size()){
							_closeConn();							
							throw new DBDocumentExtractorException("Error, mismatch the number of parameters of the sql query and DBQueryDocument");							
						}
					}
				}
				catch (SQLException e) {
					_closeConn();					
					throw new DBDocumentExtractorException(e.getMessage());				
				}
			}
			else{
				_closeConn();				
				throw new DBDocumentExtractorException("Error, " + dbQueryName + " AttributesOUT, only one CLOB or BLOB is supported");	
			}
		}
	}
	
	/**
	 * <p>Iterates over and returns a resultSet DBDocument obtained for each record of the cursor.<br>
	 * When there are no more record closes the connection.</p>
	 * @return Returns a DBDocument object representing a document extracted from the DB.
	 * @throws DBDocumentExtractorException
	 */
	public DBDocument next() throws DBDocumentExtractorException {
		DBDocument result = null;
		try {
			if (rs.next()){
				result = extractData(dbQueryConfig.getAttOUT());
			}
			else{
				_closeConn();
			}
		}
		catch (SQLException e) {
			_closeConn();			
			throw new DBDocumentExtractorException("Error during db document extraction");			
		}
		return result;
	}
	
	
	/**
	 * Validates that there is a single output attribute of type CLOB or BLOB
	 * @param attributes
	 * @return true if there is only one, false if there are none or more than one
	 */
	private boolean _validateATTOUT(List<DBAttributeConfig> attributes){
		int cant = 0;
		boolean validate = true;
		
		for(DBAttributeConfig a : attributes){
			if (a.getArg("type").equalsIgnoreCase(DataTypes.BLOB.toString()) || a.getArg("type").equalsIgnoreCase(DataTypes.CLOB.toString())){
				cant++;
			}
		}
		if (cant != 1){
			validate = false;
		}
		return validate;
	}
	
	
	private void _closeConn(){
		try {
			pstmt.close();
			rs.close();
			conn.close();
		} catch (Exception e) { /* do nothing */ }
	}

	
	public abstract DBDocument extractData(final List<DBAttributeConfig> attributes)  throws DBDocumentExtractorException;
	
}